﻿@using Radzen
@using Microsoft.AspNetCore.Components.Forms
@using System.Linq.Expressions
@using System.Globalization
@using Microsoft.JSInterop

@typeparam TValue
@inherits RadzenComponent
@implements IRadzenFormComponent
@if (Visible)
{
    <div @ref="@Element" @attributes="Attributes" class="@GetCssClass()" style="@getStyle()" id="@GetId()">
        @if (!Inline)
        {
            <span class="@($"rz-calendar rz-calendar-w-btn{(Disabled ? " rz-state-disabled" : "")}")" style="width:100%">
                <input @ref="@input" disabled="@Disabled" readonly="@ReadOnly" value="@FormattedValue" tabindex="@TabIndex"
                       @onchange="@ParseDate" autocomplete="off" type="text" 
                       class="rz-inputtext" id="@Name" placeholder="@Placeholder" />
                <button onclick="@getOpenPopup()" class="@($"rz-datepicker-trigger rz-calendar-button rz-button rz-button-icon-only{(Disabled ? " rz-state-disabled" : "")}")" tabindex="-1" type="button">
                    <span aria-hidden="true" class="rz-button-icon-left  rzi rzi-calendar"></span><span class="rz-button-text"></span>
                </button>
                @if (AllowClear && HasValue)
                {
                    <i class="rz-dropdown-clear-icon rzi rzi-times" @onclick="@Clear" @onclick:stopPropagation="true"></i>
                }
            </span>
        }
        <div id="@PopupID" style=@PopupStyle class="@($"{(Inline ? "rz-datepicker-inline " : "")}rz-datepicker")" @onclick:preventDefault="true">
            <div class="rz-datepicker-group">
                @if (!TimeOnly)
                {
                    <div class="rz-datepicker-header">
                        <a href="javascript:void(0)" class="rz-datepicker-prev" @onclick="@(async () => { if (!Disabled) { Value = CurrentDate.AddMonths(-1); await OnChange(); } })">
                            <span class="rz-datepicker-prev-icon rzi rzi-chevron-left"></span>
                        </a>
                        <a href="javascript:void(0)" class="rz-datepicker-next" @onclick="@(async () => { if (!Disabled) { Value = CurrentDate.AddMonths(1); await OnChange(); } })">
                            <span class="rz-datepicker-next-icon rzi rzi-chevron-right"></span>
                        </a>
                        <div class="rz-datepicker-title" style="height:40px;">
                            <RadzenDropDown @ref="monthDropDown" Style="height:auto;width:120px;margin-top:5px;text-align:left;" TabIndex="-1"
                                            TValue="int" Value="@CurrentDate.Month" Disabled="@Disabled" Data="@months" TextProperty="Name" ValueProperty="Value"
                                            Change="@(async (args) => { await SetMonth(int.Parse(args.ToString())); })" />
                            <RadzenDropDown @ref="yearDropDown" Style="height:auto;width:80px;margin-top:5px;text-align:left;" TabIndex="-1"
                                            TValue="int" Value="@CurrentDate.Year" Disabled="@Disabled" Data="@years" TextProperty="Name" ValueProperty="Value"
                                            Change="@(async (args) => { await SetYear(int.Parse(args.ToString())); })" />
                        </div>
                    </div>
                    <div class="rz-datepicker-calendar-container">
                        <table class="rz-datepicker-calendar" style="@(Inline ? "" : "width:100%")">
                            <thead>
                                <tr>
                                    @foreach (var day in AbbreviatedDayNames)
                                    {
                                        <th scope="col">
                                            <span>@day</span>
                                        </th>
                                    }
                                </tr>
                            </thead>
                            <tbody>
                                @{int dayNumber = 0;}
                                @for (int i = 0; i < 6; i++)
                                {
                                    <tr>
                                        @for (int j = 0; j < 7; j++)
                                        {
                                            DateTime date = StartDate.AddDays(dayNumber++);
                                            var dateArgs = DateAttributes(date);

                                            @if (CurrentDate.Month == date.Month)
                                            {
                                                <td @attributes="@(dateArgs.Attributes)" onmouseup=@(Inline || ShowTime ? "" : $"Radzen.closePopup('{PopupID}')")
                                                    @onclick="@(async () => { if (!Disabled && !dateArgs.Disabled) { await SetDay(date); } })">
                                                    <span class=@($"rz-state-default{(CurrentDate.Year == date.Year && CurrentDate.Day == date.Day ? " rz-state-active" : "")}{(!dateArgs.Disabled ? "" : " rz-state-disabled")}")>@date.Day</span>
                                                </td>
                                            }
                                            else
                                            {
                                                <td @attributes="@(dateArgs.Attributes)" class="rz-datepicker-other-month" onmouseup=@(Inline || ShowTime ? "" : $"Radzen.closePopup('{PopupID}')")
                                                    @onclick="@(async () => { if (!Disabled && !dateArgs.Disabled) { await SetDay(date); } })">
                                                    <span class="rz-state-default rz-state-disabled">@date.Day</span>
                                                </td>
                                            }
                                        }
                                    </tr>
                                }
                            </tbody>
                        </table>
                    </div>
                }
            </div>
            @if (ShowTime)
            {
        <div class="rz-timepicker">
            <RadzenNumeric TValue="int" Disabled="@Disabled" Value="@(HourFormat == "12" ? ((CurrentDate.Hour + 11) % 12) + 1 : CurrentDate.Hour)" 
                           Min="@(HourFormat == "12" ? 1 : -1)" Max="@(HourFormat == "12" ? 12 : 24)" TValue="double" Step="@HoursStep"
                           Change="@UpdateHour" class="rz-hour-picker" @oninput=@OnUpdateHourInput />
            <div class="rz-separator">
                <span>:</span>
            </div>
            <RadzenNumeric TValue="int" Disabled="@Disabled" Value="CurrentDate.Minute" TValue="double" Step="@MinutesStep" Min="0" Max="59"
                           Change="@UpdateMinutes" class="rz-minute-picker" @oninput=@OnUpdateHourMinutes />
            @if (ShowSeconds)
            {
                <div class="rz-separator">
                    <span>:</span>
                </div>
                <RadzenNumeric TValue="int" Disabled="@Disabled" Value="CurrentDate.Second" TValue="double" Step="@SecondsStep" Min="0" Max="59"
                               Change="@UpdateSeconds" class="rz-second-picker" @oninput=@OnUpdateHourSeconds />
            }
            @if (HourFormat == "12")
            {
                <div class="rz-ampm-picker">
                    <a href="javascript:void(0)" @onclick="@AmToPm">
                        <span class="rzi rzi-chevron-up"></span>
                    </a>
                    <span>@CurrentDate.ToString("tt")</span>
                    <a href="javascript:void(0)" @onclick="@PmToAm">
                        <span class="rzi rzi-chevron-down"></span>
                    </a>
                </div>
            }
            @if (ShowTimeOkButton)
            {
                <button type="button" class="rz-button rz-button-md btn-secondary" style="width:60px;padding:0px;margin-left:10px;"
                        @onclick="@OkClick"
                        onmouseup="@($"Radzen.closePopup('{PopupID}')")">
                    <span class="rz-button-text">Ok</span>
                </button>
            }
        </div>
            }
        </div>
    </div>
}
@code {
    RadzenDropDown<int> monthDropDown;
    RadzenDropDown<int> yearDropDown;

    async Task AmToPm()
    {
        if (amPm == "am" && !Disabled) 
        { 
            amPm = "pm"; 
                            
            var currentHour = ((CurrentDate.Hour + 11) % 12) + 1;

            var newHour = currentHour - 12;

            if(newHour < 1)
            {
                newHour = currentHour;
            }

            var newValue = new DateTime(CurrentDate.Year, CurrentDate.Month, CurrentDate.Day, newHour, CurrentDate.Minute, CurrentDate.Second);

            if(!object.Equals(newValue, Value))
            {
                Value = newValue;
                await OnChange();
            } 
        } 
    }

    async Task PmToAm()
    {
        if (amPm == "pm" && !Disabled) 
        { 
            amPm = "am"; 

            var currentHour = ((CurrentDate.Hour + 11) % 12) + 1;

            var newHour = currentHour + 12;

            if(newHour > 23)
            {
                newHour = 0;
            }

            var newValue = new DateTime(CurrentDate.Year, CurrentDate.Month, CurrentDate.Day, newHour, CurrentDate.Minute, CurrentDate.Second);

            if(!object.Equals(newValue, Value))
            {
                Value = newValue;
                await OnChange();
            } 
        } 
    }
    
    int? hour;
    void OnUpdateHourInput(ChangeEventArgs args)
    {
        var value = $"{args.Value}";
        if(!string.IsNullOrWhiteSpace(value))
        {
            hour = (int)Convert.ChangeType(value, typeof(int));
        }
    }

    int? minutes;
    void OnUpdateHourMinutes(ChangeEventArgs args)
    {
        var value = $"{args.Value}";
        if(!string.IsNullOrWhiteSpace(value))
        {
            minutes = (int)Convert.ChangeType(value, typeof(int));
        }
    }

    int? seconds;
    void OnUpdateHourSeconds(ChangeEventArgs args)
    {
        var value = $"{args.Value}";
        if(!string.IsNullOrWhiteSpace(value))
        {
            seconds = (int)Convert.ChangeType(value, typeof(int));
        }
    }

    async Task UpdateHour(int v)
    {
        var newHour = HourFormat == "12" && CurrentDate.Hour > 12 ? v + 12 : v;

        var newValue = new DateTime(CurrentDate.Year, CurrentDate.Month, CurrentDate.Day, newHour > 23 || newHour < 0 ? 0 : newHour, CurrentDate.Minute, CurrentDate.Second);

        if(!object.Equals(newValue, Value))
        {
            hour = newValue.Hour;
            Value = newValue;
            await OnChange();
        }
    }

    async Task UpdateMinutes(int v)
    {
        var newValue = new DateTime(CurrentDate.Year, CurrentDate.Month, CurrentDate.Day, CurrentDate.Hour, v, CurrentDate.Second);

        if(!object.Equals(newValue, Value))
        {
            minutes = newValue.Minute;
            Value = newValue;
            await OnChange();
        }
    }

    async Task UpdateSeconds(int v)
    {
        var newValue = new DateTime(CurrentDate.Year, CurrentDate.Month, CurrentDate.Day, CurrentDate.Hour, CurrentDate.Minute, v);

        if(!object.Equals(newValue, Value))
        {
            seconds = newValue.Second;
            Value = newValue;
            await OnChange();
        }
    }

    async Task OkClick()
    {
        if (!Disabled) 
        { 
            DateTime date = CurrentDate;

            if(CurrentDate.Hour != hour && hour != null)
            {
                var newHour = HourFormat == "12" && CurrentDate.Hour > 12 ? hour.Value + 12 : hour.Value;
                date = new DateTime(CurrentDate.Year, CurrentDate.Month, CurrentDate.Day, newHour > 23 || newHour < 0 ? 0 : newHour, CurrentDate.Minute, CurrentDate.Second);
            }

            if(CurrentDate.Minute != minutes && minutes != null)
            {
                date = new DateTime(CurrentDate.Year, CurrentDate.Month, CurrentDate.Day, CurrentDate.Hour, minutes.Value, CurrentDate.Second);
            }

            if(CurrentDate.Second != seconds && seconds != null)
            {
                date = new DateTime(CurrentDate.Year, CurrentDate.Month, CurrentDate.Day, CurrentDate.Hour, CurrentDate.Minute, seconds.Value);
            }

            Value = date;

            await OnChange(); 
        
            if(monthDropDown != null)
            {
                await monthDropDown.ClosePopup();
            } 
        
            if(yearDropDown != null) 
            { 
                await yearDropDown.ClosePopup();
            } 
        }
    }

    class NameValue
    {
        public string Name { get; set; }
        public int Value { get; set; }
    }

    IList<NameValue> months;
    IList<NameValue> years;

    protected override void OnInitialized()
    {
        base.OnInitialized();

        months = Enumerable.Range(1, 12).Select(i => new NameValue() { Name = DateTimeFormatInfo.CurrentInfo.GetMonthName(i), Value = i }).ToList();
        years = Enumerable.Range(int.Parse(YearRange.Split(':').First()), int.Parse(YearRange.Split(':').Last()) - int.Parse(YearRange.Split(':').First()) + 1)
            .Select(i => new NameValue() { Name = $"{i}", Value = i }).ToList();
    }

    [Parameter]
    public bool AllowClear { get; set; }

    [Parameter]
    public int TabIndex { get; set; } = 0;

    string amPm = "am";

    [Parameter]
    public string Name { get; set; }

    DateTime? _dateTimeValue;

    DateTime? DateTimeValue
    {
        get
        {
            return _dateTimeValue;
        }
        set
        {
            if (_dateTimeValue != value)
            {
                _dateTimeValue = value;
                Value = value;
            }
        }
    }

    [Parameter]
    public Action<DateRenderEventArgs> DateRender { get; set; }

    DateRenderEventArgs DateAttributes(DateTime value)
    {
        var args = new Radzen.DateRenderEventArgs() { Date = value, Disabled = false };

        if (DateRender != null)
        {
            DateRender(args);
        }

        return args;
    }

    object _value;

    [Parameter]
    public object Value
    {
        get
        {
            return _value;
        }
        set
        {
            if (_value != value)
            {
                _value = value;

                DateTimeOffset? offset = value as DateTimeOffset?;
                if (offset != null && offset.HasValue)
                {
                    _dateTimeValue = offset.Value.DateTime;
                    _value = _dateTimeValue;
                }
                else
                {
                    DateTimeValue = value as DateTime?;

                    if (DateTimeValue.HasValue && DateTimeValue.Value == default(DateTime))
                    {
                        _value = null;
                        _dateTimeValue = null;
                    }
                }
            }
        }
    }

    private DateTime CurrentDate
    {
        get
        {
            return HasValue && DateTimeValue.Value != default(DateTime) ? DateTimeValue.Value : DateTime.Today;
        }
    }

    private DateTime StartDate
    {
        get
        {
            var firstDayOfTheMonth = new DateTime(CurrentDate.Year, CurrentDate.Month, 1);

            int diff = (7 + (firstDayOfTheMonth.DayOfWeek - DateTimeFormatInfo.CurrentInfo.FirstDayOfWeek)) % 7;
            return firstDayOfTheMonth.AddDays(-1 * diff).Date;
        }
    }

    IList<string> _abbreviatedDayNames;
    IList<string> AbbreviatedDayNames
    {
        get
        {
            if (_abbreviatedDayNames == null)
            {
                _abbreviatedDayNames = new List<string>();

                for (int i = (int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek; i < 7; i++)
                {
                    _abbreviatedDayNames.Add(CultureInfo.CurrentCulture.DateTimeFormat.AbbreviatedDayNames[i]);
                }

                for (int i = 0; i < (int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek; i++)
                {
                    _abbreviatedDayNames.Add(CultureInfo.CurrentCulture.DateTimeFormat.AbbreviatedDayNames[i]);
                }
            }
            return _abbreviatedDayNames;
        }
    }

    public bool IsBound
    {
        get
        {
            return ValueChanged.HasDelegate;
        }
    }

    public bool HasValue
    {
        get
        {
            return DateTimeValue.HasValue;
        }
    }

    public string FormattedValue
    {
        get
        {
            return string.Format("{0:" + DateFormat + "}", Value);
        }
    }

    IRadzenForm _form;

    [CascadingParameter]
    public IRadzenForm Form
    {
        get
        {
            return _form;
        }
        set
        {
            if (_form != value && value != null)
            {
                _form = value;
                _form.AddComponent(this);
            }
        }
    }

    protected ElementReference input;

    protected async Task ParseDate()
    {
        DateTime? newValue;
        DateTime value;
        var inputValue = await JSRuntime.InvokeAsync<string>("Radzen.getInputValue", input);

        var valid = DateTime.TryParseExact(inputValue, DateFormat, null, DateTimeStyles.None, out value);
        var nullable = Nullable.GetUnderlyingType(typeof(TValue)) != null;

        if (!valid)
        {
            valid = DateTime.TryParse(inputValue, out value);
        }

        if (valid)
        {
            newValue = value;
        }
        else
        {
            newValue = null;

            if (nullable)
            {
                await JSRuntime.InvokeAsync<string>("Radzen.setInputValue", input, "");
            }
            else
            {
                await JSRuntime.InvokeAsync<string>("Radzen.setInputValue", input, FormattedValue);
            }

        }

        if (DateTimeValue != newValue && (newValue != null || nullable))
        {
            DateTimeValue = newValue;
            await ValueChanged.InvokeAsync((TValue)Value);

            if (FieldIdentifier.FieldName != null)
            {
                EditContext?.NotifyFieldChanged(FieldIdentifier);
            }

            await Change.InvokeAsync(DateTimeValue);
            StateHasChanged();
        }
    }

    async Task Clear()
    {
        Value = null;

        await ValueChanged.InvokeAsync(default(TValue));

        if (FieldIdentifier.FieldName != null)
        {
            EditContext?.NotifyFieldChanged(FieldIdentifier);
        }

        await Change.InvokeAsync(DateTimeValue);
        StateHasChanged();
    }

    [Parameter]
    public bool Inline { get; set; }

    [Parameter]
    public bool TimeOnly { get; set; }

    [Parameter]
    public bool ReadOnly { get; set; }

    [Parameter]
    public bool Disabled { get; set; }

    [Parameter]
    public bool ShowTime { get; set; }

    [Parameter]
    public bool ShowSeconds { get; set; }

    [Parameter]
    public string HoursStep { get; set; }

    [Parameter]
    public string MinutesStep { get; set; }

    [Parameter]
    public string SecondsStep { get; set; }

    enum StepType
    {
        Hours,
        Minutes,
        Seconds
    }

    double getStep(StepType type)
    {
        double step = 1;

        if (type == StepType.Hours)
        {
            step = parseStep(HoursStep);
        }
        else if (type == StepType.Minutes)
        {
            step = parseStep(MinutesStep);
        }
        else if (type == StepType.Seconds)
        {
            step = parseStep(SecondsStep);
        }

        return step;
    }


    double parseStep(string step)
    {
        return string.IsNullOrEmpty(step) || step == "any" ? 1 : double.Parse(step.Replace(",", "."), System.Globalization.CultureInfo.InvariantCulture);
    }

    [Parameter]
    public bool ShowTimeOkButton { get; set; } = true;

    [Parameter]
    public string DateFormat { get; set; }

    [Parameter]
    public string YearRange { get; set; } = "1950:2050";
    /*
    [Parameter]
    public string SelectionMode { get; set; } = "single";
    */
    [Parameter]
    public string HourFormat { get; set; } = "24";

    /*
    [Parameter]
    public bool Utc { get; set; } = true;
    */

    [Parameter]
    public string Placeholder { get; set; }

    [Parameter]
    public EventCallback<DateTime?> Change { get; set; }

    [Parameter]
    public EventCallback<TValue> ValueChanged { get; set; }

    string contentStyle = "display:none;";

    private string getStyle()
    {
        return $"display: inline-block;{(Inline ? "overflow:auto;": "")}{(Style != null ? Style : "")}";
    }

    public void Close()
    {
        if (!Disabled)
        {
            contentStyle = "display:none;";
            StateHasChanged();
        }
    }

    private string PopupStyle
    {
        get
        {
            if (Inline)
            {
                return "white-space: nowrap";
            }
            else
            {
                return $"width: 320px; {contentStyle}";
            }
        }
    }

    async System.Threading.Tasks.Task OnChange()
    {
        if ((typeof(TValue) == typeof(DateTimeOffset) || typeof(TValue) == typeof(DateTimeOffset?)) && Value != null)
        {
            DateTimeOffset? offset = DateTime.SpecifyKind((DateTime)Value, DateTimeKind.Utc);
            await ValueChanged.InvokeAsync((TValue)(object)offset);
        }
        else
        {
            await ValueChanged.InvokeAsync((TValue)Value);
        }

        if (FieldIdentifier.FieldName != null) { EditContext?.NotifyFieldChanged(FieldIdentifier); }
        await Change.InvokeAsync(DateTimeValue);

    }

    protected override string GetComponentCssClass()
    {
        var fieldCssClass = FieldIdentifier.FieldName != null ? EditContext?.FieldCssClass(FieldIdentifier) : "";

        var classNames = new List<string>();

        classNames.Add(base.GetComponentCssClass());

        if (Inline)
        {
            classNames.Add("rz-calendar-inline");
        }

        if (!string.IsNullOrEmpty(fieldCssClass))
        {
            classNames.Add(fieldCssClass);
        }

        return string.Join(" ", classNames);
    }

    private async System.Threading.Tasks.Task SetDay(DateTime newValue)
    {
        var currentValue = HasValue ? DateTimeValue.Value : CurrentDate;

        if (newValue != DateTimeValue)
        {
            DateTimeValue = newValue;
            await OnChange();
            Close();
        }
    }

    private async System.Threading.Tasks.Task SetMonth(int month)
    {
        var currentValue = HasValue ? DateTimeValue.Value : CurrentDate;
        var newValue = new DateTime(currentValue.Year, month, Math.Min(currentValue.Day, DateTime.DaysInMonth(currentValue.Year, month)), currentValue.Hour, currentValue.Minute, currentValue.Second);

        if (newValue != DateTimeValue)
        {
            DateTimeValue = newValue;
            await OnChange();
            Close();
        }
    }

    private async System.Threading.Tasks.Task SetYear(int year)
    {
        var currentValue = HasValue ? DateTimeValue.Value : CurrentDate;
        var newValue = new DateTime(year, currentValue.Month, Math.Min(currentValue.Day, DateTime.DaysInMonth(year, currentValue.Month)), currentValue.Hour, currentValue.Minute, currentValue.Second);

        if (newValue != DateTimeValue)
        {
            DateTimeValue = newValue;
            await OnChange();
            Close();
        }
    }

    private string getOpenPopup()
    {
        return !Disabled && !ReadOnly && !Inline ? $"Radzen.togglePopup(this.parentNode, '{PopupID}')" : "";
    }

    [CascadingParameter]
    public EditContext EditContext { get; set; }

    public FieldIdentifier FieldIdentifier { get; private set; }

    [Parameter]
    public Expression<Func<TValue>> ValueExpression { get; set; }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        var shouldClose = false;

        if (parameters.DidParameterChange(nameof(Visible), Visible))
        {
            var visible = parameters.GetValueOrDefault<bool>(nameof(Visible));
            shouldClose = !visible;
        }

        await base.SetParametersAsync(parameters);

        if (shouldClose && !firstRender)
        {
            await JSRuntime.InvokeVoidAsync("Radzen.destroyPopup", PopupID);
        }

        if (EditContext != null && ValueExpression != null && FieldIdentifier.Model != EditContext.Model)
        {
            FieldIdentifier = FieldIdentifier.Create(ValueExpression);
            EditContext.OnValidationStateChanged += ValidationStateChanged;
        }
    }

    private void ValidationStateChanged(object sender, ValidationStateChangedEventArgs e)
    {
        StateHasChanged();
    }

    public override void Dispose()
    {
        base.Dispose();

        if (EditContext != null)
        {
            EditContext.OnValidationStateChanged -= ValidationStateChanged;
        }

        Form?.RemoveComponent(this);

        if (IsJSRuntimeAvailable)
        {
            JSRuntime.InvokeVoidAsync("Radzen.destroyPopup", PopupID);
        }
    }

    public object GetValue()
    {
        return Value;
    }

    private string PopupID
    {
        get
        {
            return $"popup{UniqueID}";
        }
    }

    private bool firstRender = true;

    protected override Task OnAfterRenderAsync(bool firstRender)
    {
        this.firstRender = firstRender;

        return base.OnAfterRenderAsync(firstRender);
    }
}